package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"
)

//主要目的，实现在client 中的请求重试以及多次失败的记录
//并且程序重启后如何继续处理多次失败的记录
type MyUrl struct {
	Url      string
	TryCount int
}

type TryCount int

func main() {
	url := "http://localhost:2021"
	myUrl := MyUrl{Url: url}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
	ctx = context.WithValue(ctx, TryCount(0), 3)
	defer cancel()
	var slowReqCh chan MyUrl = make(chan MyUrl)
	defer close(slowReqCh)

	file, err := os.OpenFile("../slow/slow.txt", os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		log.Fatalf("初始化慢请求记录文件失败 %s \n", err)
	}

	go handleVerySlowReq(slowReqCh, file)
	doRequest(ctx, myUrl, slowReqCh)

}

func doRequest(ctx context.Context, myUrl MyUrl, slowReqCh chan<- MyUrl) {
	//先取得重试次数
	key := TryCount(0)
	tryCount, ok := ctx.Value(key).(int)
	if !ok {
		log.Fatalf("获取重试次数失败 \n")
	}

	transport := http.Transport{
		DisableKeepAlives: true,
	}

	httpClient := http.Client{
		Transport: &transport,
	}

	req, err := http.NewRequest(http.MethodGet, myUrl.Url, nil)
	if err != nil {
		fmt.Printf("创建请求失败 %s \n", err)
		log.Fatal(err)
	}
	reqWithCtx := req.WithContext(ctx)

	resp, err := httpClient.Do(reqWithCtx)

	if errors.Is(err, context.DeadlineExceeded) {
		fmt.Printf("请求超时 %s, 进入重试\n", err)

		var tryCountStart int = 1
		for tryCount > 0 && errors.Is(err, context.DeadlineExceeded) {
			fmt.Printf("请求重试第%d次\n", tryCountStart)

			tryCtx, cancel := context.WithTimeout(context.TODO(), time.Second*3)
			tryReqWithCtx := req.WithContext(tryCtx)
			resp, err = httpClient.Do(tryReqWithCtx)
			if err != nil {
				fmt.Printf("重试请求失败 %s \n", err)
			}
			tryCountStart++
			tryCount--

			//手动调用cancel
			cancel()
		}

		if err != nil {
			fmt.Printf("重试请求失败 %s\n", err)
			slowReqCh <- myUrl
			log.Fatal(err)
		}

	} else {
		if err != nil {
			fmt.Printf("请求失败 %s\n", err)
			log.Fatal(err)
		}

	}

	defer func() {
		if resp != nil {
			resp.Body.Close()
		}
	}()

	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("读取数据失败 %s \n", err)
		log.Fatal(err)
	}

	fmt.Println(string(respData))
}

func handleVerySlowReq(slowReqCh <-chan MyUrl, file *os.File) {

	select {
	case slow := <-slowReqCh:
		slowJson, _ := json.Marshal(slow)
		_, err := file.Write(slowJson)
		if err != nil {
			log.Printf("写入慢请求失败 %s \n", err)
		}
	}
}
